#!/usr/bin/env perl 
#
# %CopyrightBegin%
# 
# Copyright Ericsson AB 1999-2022. All Rights Reserved.
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 
# %CopyrightEnd%
#
use strict;
use File::Basename;

#
# Description:
#   Packages one erlang module in a form that can be preloaded (C source
#   for Unix or resource script for Windows).  The output is written to
#   standard output.
#
# Usage:
#	make_preload [ Options ] file.{jam,beam}
#
# Options:
#	-rc		Produce a resource script rather than C source.
#
# Author:
#    Bjorn Gustavsson
#

my $gen_rc = 0;
my $gen_old = 0;
my $windres = 0;
my $file;

my $progname = basename($0);

while (@ARGV && $ARGV[0] =~ /^-(\w+)/) {
    my $opt = shift;
    if ($opt eq '-rc') {
	$gen_rc = 1;
    } elsif ($opt eq '-windres') {
	$windres = 1;
    } elsif ($opt eq '-old') {
	$gen_old = 1;
    } else {
	usage("bad option: $opt");
    }
}

print header();

my @modules;
my $num = 1;

foreach $file (@ARGV) {
    local($/);

    usage("not a beam file")
	unless $file =~ /\.beam$/;
    my $module = basename($file, ".beam");
    if ($gen_rc) {
	my $win_file;
        ($win_file) = split("\n", `(w32_path.sh -d $file)`);
	print "$num ERLANG_CODE \"$win_file\"\n";
	push(@modules, "   ", -s $file, "L, $num, ",
	     length($module), ",\"$module\",\n");
	$num++;
    } else {
	my $i;
	my $comment = '';

	open(FILE, $file) or error("failed to read $file: $!");
	binmode(FILE);
	$_ = <FILE>;
	$_ = beam_strip($_, $file);
	close(FILE);
	
	push(@modules, "  {\"$module\", " . length($_) . ", preloaded_$module},\n");
	print "const unsigned preloaded_size_$module = ", length($_), ";\n";
	print "const unsigned char preloaded_$module", "[] = {\n";
	for ($i = 0; $i < length($_); $i++) {
	    if ($i % 8 == 0 && $comment ne '') {
		$comment =~ s@/\*@..@g; # Comment start -- avoid warning.
		$comment =~ s@\*/@..@g; # Comment terminator.
		print " /* $comment */\n  ";
		$comment = '';
	    }
	    my $c = ord(substr($_, $i, 1));
	    printf("0x%02x,", $c);
	    $comment .= (32 <= $c && $c < 127) ? chr($c) : '.';
	}
	$comment =~ s@\*/@..@g; # Comment terminator.
	print "     " x (8-($i % 8)), " /* $comment */\n};\n";
    }
}

if ($windres) {
    $modules[$#modules] =~ s/,$//;
}

if ($gen_rc) {
    print "#include <beam.rc>\n";
    $num--;
    print "\n0 ERLANG_DICT\n";
    print "BEGIN\n";
    print "    $num,\n";
    print @modules;
    print "END\n";
} elsif ($gen_old) {
    print "const struct {\n";
    print "   char* name;\n";
    print "   int size;\n";
    print "   const unsigned char* code;\n";
    print "} pre_loaded[] = {\n";
    foreach (@modules) {
	print;
    }
    print "  {0, 0, 0}\n";
    print "};\n";
}

sub usage {
    warn "$progname: ", @_, "\n";
    die "usage: $progname -o output-directory file.{jam,beam}\n";
}

sub error {
    die "$progname: ", @_, "\n";
}

sub beam_strip {
    my($beam,$file) = @_;


    my $size_left = length($beam);
    my %chunk;
    my %needed_chunk = ('Code' => 1,
			'AtU8' => 1,
			'ImpT' => 1,
			'ExpT' => 1,
			'StrT' => 1,
			'FunT' => 1,
			'LitT' => 1,
			'Type' => 1
        );

    die "$file: can't read Beam files for OTP R4 or earlier (sorry)"
	if $beam =~ /^\x7fBEAM!/;

    #
    # Read and verify the head of the IFF file.
    #
    
    my ($id, $size, $beam_id) = unpack("a4Na4", $beam);

    return $beam		# It might be compressed.
	unless $id eq 'FOR1';
#    die "not a BEAM file: no IFF 'FOR1' chunk"
#	unless $id eq 'FOR1';
    $size_left -= 8;
    die "form size $size greater than size ", $size_left, " of module"
	if $size > $size_left;
    $size_left -= 4;
    die "$file: not a BEAM file: IFF form type is not 'BEAM'"
	unless $beam_id eq 'BEAM';

    #
    # Read all IFF chunks.
    #

    $beam = substr($beam, 12, $size_left);
    while ($size_left > 0) {
	($id, $size) = unpack("a4N", $beam);
	$size_left -= 8;
	die "chunk size $size greater than size ", $size_left, " of module"
	    if $size > $size_left;
	$size = 4*int(($size+3)/4);
	$chunk{$id} = substr($beam, 0, 8+$size);
	$beam = substr($beam, 8+$size);
	$size_left = length($beam);
    }

    #
    # Abort if there is no new-style 'AtU8' atom chunk.
    #

    exists $chunk{'AtU8'} or
        die "$file: no 'AtU8' chunk (re-compile with " .
        "OTP 20 or later)\n";

    #
    # Create a new beam file with only the useful chunk types.
    #

    my @chunks;
    foreach (sort keys %chunk) {
	push(@chunks, $chunk{$_})
	    if $needed_chunk{$_};
    }
    $beam = join('', @chunks);
    join('', "FOR1", pack("N", length($beam)+4), "BEAM", $beam);
}

sub header {
    my $time = localtime;
    <<END;
/*
 * Do *not* edit this file. It was automatically generated by
 * `$progname'.
 */
END
}
